package utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import com.vaadin.ui.ComboBox;

import views.annotations.Action;
import views.annotations.ArgAnnotation;
import views.annotations.ArgAnnotation.ArgAnnotations;
import views.annotations.View;
import views.annotations.View.Views;
import views.annotations.Buscar;
import views.components.SelectorManyToOne;
import views.components.SelectorOneToMany;
import app.structure.Arg;
import dao.interfaces.IBase;

public class ReflectionUtils {


	
	/**
	 * Retorna un Field de vaadin si Fieldname es de tipo Enum retornara un
	 * combo amb tots els valors del enum, si te una anotacio @manyToOne
	 * retornara un selector manyToOne, si conte una anotacio @OneToMany
	 * retornara un selector OneToMany
	 * 
	 * @param fieldName
	 * @param myClass
	 * @return VaadinField
	 */
	@SuppressWarnings("unchecked")
	public static com.vaadin.ui.Field<?> construirSelector(String fieldName,
			Class<IBase> myClass) {

		Field field = ReflectionUtils.findField(myClass, fieldName);

		Class<?> type = field.getType();
		if (type.isEnum()) {

			List<?> asList = Arrays.asList(type.getEnumConstants());
			return new ComboBox(fieldName, asList);

		}

		ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);

		if (manyToOne != null) {
			return new SelectorManyToOne((Class<IBase>) type);
		}

		OneToMany oneToMany = field.getAnnotation(OneToMany.class);

		if (oneToMany != null) {

			Type genericFieldType = field.getGenericType();

			if (genericFieldType instanceof ParameterizedType) {
				ParameterizedType aType = (ParameterizedType) genericFieldType;
				Type[] fieldArgTypes = aType.getActualTypeArguments();
				for (Type fieldArgType : fieldArgTypes) {
					return new SelectorOneToMany((Class<IBase>) fieldArgType);
				}
			}

		}

		return null;
	}

	/**
	 * Retorna un Field de Myclass o les superclases que coincidixca amb
	 * fieldName
	 * 
	 * @param myClass
	 * @param fieldName
	 * @return Field or null
	 */
	public static Field findField(Class<?> myClass, String fieldName) {

		Field field = null;

		while (myClass != Object.class && field == null) {

			try {
				field = myClass.getDeclaredField(fieldName);
				field.setAccessible(true);
			} catch (Exception e) {
				// si no existeix busquem a la superclase
				myClass = myClass.getSuperclass();
			}
		}

		return field;
	}

	/**
	 * Retorna un Method amb nom myMethod en Myclass o les superclases que tinga
	 * el mateix nombre de parametres y clase que ArgumentTypes
	 * 
	 * @param myClass
	 * @param methodName
	 * @param classes
	 * @return Method or null
	 */
	public static Method findMethod(Class<?> myClass, String methodName,
			Class<?>[] classes) {

		Method method = null;

		while (myClass != Object.class && method == null) {

			try {
				method = myClass.getDeclaredMethod(methodName, classes);
				method.setAccessible(true);
			} catch (Exception e) {
				// si no existeix busquem a la superclase
				myClass = myClass.getSuperclass();
			}
		}

		return method;
	}

	/**
	 * Retorna un HashMap<String, Integer> amb els noms dels metodos y el
	 * secLevels de la @action de MyClass y les superclases
	 * 
	 * @param myClass
	 * @return HashMap<String, Integer>
	 */
	public static HashMap<String, Integer> getActionAnnotations(Class<?> myClass) {

		HashMap<String, Integer> hashMap = new HashMap<String, Integer>();
		Action action;

		// recorrerem tots els metodos de myClass i superclases
		// si el metodo te anotacio action
		// ens guardem el nom i seclevel

		while (myClass != Object.class) {

			for (Method method : myClass.getDeclaredMethods()) {

				action = method.getAnnotation(Action.class);

				if (action != null)
					hashMap.put(method.getName(), action.secLevel());

			}
			myClass = myClass.getSuperclass();

		}

		return hashMap;
	}

	/**
	 * Retorna una llista de Arg dins de myClass @view(fields)
	 * 
	 * @view(fields)
	 * 
	 * @param myClass
	 * @param viewName
	 * @return List<Arg>
	 */
	public static ArrayList<Arg> getArgsView(Class<?> myClass, String viewName) {

		ArrayList<Arg> args = new ArrayList<Arg>();
		Iterator<String> itViewFields;
		String viewField;

		// agafem @view(fields) de viewName en myClass
		ArrayList<String> viewFields = ReflectionUtils
				.getViewAnnotationElements(myClass, viewName, "fields");

		// comparem els atributs de clase amb els camps que volem mostrar
		// si coincideixen els pasem a args

		for (Field field : myClass.getDeclaredFields()) {

			itViewFields = viewFields.iterator();

			while (itViewFields.hasNext()) {

				viewField = (String) itViewFields.next();

				if (field.getName().equalsIgnoreCase(viewField)) {

					args.add(new Arg(viewField, field.getType().getSimpleName()));

					// com ja hem trobat este viewField el lleve de la llista
					itViewFields.remove();
					// com ja hem trobat este field pasem al seguent
					break;

				}
			}
		}

		return args;
	}

	/**
	 * Retorna un HashMap<String, Arg> amb les @ArgAnnotations de myClass
	 * 
	 * @param myClass
	 * @return HashMap<String, Arg>
	 */
	public static HashMap<String, Arg> getArgsAnnotations(Class<?> myClass) {

		HashMap<String, Arg> hashArgs = new HashMap<String, Arg>();
		Arg arg;

		ArgAnnotations argAnnotations = myClass
				.getAnnotation(ArgAnnotation.ArgAnnotations.class);

		for (ArgAnnotation argAnnotation : argAnnotations.value()) {

			arg = new Arg();
			arg.setEditor(argAnnotation.editor());
			arg.setEnabled(argAnnotation.enabled());
			arg.setName(argAnnotation.name());
			arg.setSource(argAnnotation.source());
			arg.setType(argAnnotation.type());
			arg.setValue(argAnnotation.value());
			arg.setVisible(argAnnotation.visible());
			hashArgs.put(arg.getName(), arg);
		}

		return hashArgs;
	}

	/**
	 * Retorna un HashMap<String, Arg> amb les @ArgAnnotations de MyClass en un
	 * ViewName
	 * 
	 * @param myClass
	 * @param viewName
	 * @return HashMap<String, Arg>
	 */
	public static HashMap<String, Arg> getArgsAnnotationsView(Class<?> myClass,
			String viewName) {

		HashMap<String, Arg> argsViewName = new HashMap<String, Arg>();

		ArrayList<String> viewArgs = getViewAnnotationElements(myClass,
				viewName, "args");

		Iterator<Arg> itArgAnnotations = getArgsAnnotations(myClass).values()
				.iterator();

		// recorrem les @ArgAnnotations de myClass
		// y si estan en el @view les guardem

		while (itArgAnnotations.hasNext()) {

			Arg arg = (Arg) itArgAnnotations.next();

			for (String viewArg : viewArgs)

				if (arg.getName().equalsIgnoreCase(viewArg))

					argsViewName.put(arg.getName(), arg);

		}

		return argsViewName;
	}

	/**
	 * Retorna els valors del camp valorBuscat dins de la anotacio @View amb nom
	 * viewname de myClass. Si no troba @view(fields) torna tots els fields de
	 * myClass
	 * 
	 * @param myClass
	 * @param viewName
	 * @param valorBuscat
	 * @return ArrayList<String>
	 */
	public static ArrayList<String> getViewAnnotationElements(Class<?> myClass,
			String viewName, String valorBuscat) {

		String valor = "";
		ArrayList<String> list = new ArrayList<String>();

		View view = myClass.getAnnotation(View.class);

		if (view != null) {

			if (view.name().equalsIgnoreCase(viewName))

				switch (valorBuscat.toLowerCase()) {
				case "fields":
					valor = view.fields();
					break;
				}

		} else {

			Views views = myClass.getAnnotation(View.Views.class);

			if (views != null)

				for (View v : views.value())

					if (v.name().equalsIgnoreCase(viewName))

						switch (valorBuscat.toLowerCase()) {
						case "fields":
							valor = v.fields();
							break;
						}

		}

		if (valor.length() > 0)
			for (String s : valor.split(","))
				list.add(s);

		// si busquem el camp fields i no lem trobat
		// agafem tots els camps

		if (list.isEmpty() && valorBuscat.equalsIgnoreCase("fields"))
			for (Field f : myClass.getDeclaredFields())
				list.add(f.getName());

		return list;

	}

	/**
	 * Retorna els valors del camp fields dins de la anotacio @View amb nom
	 * viewname de myClass. Si no troba fields torna tots els fields de myClass
	 * 
	 * @param myClass
	 * @param viewName
	 * @return ArrayList<String>
	 */
	public static ArrayList<String> getViewFields(Class<?> myClass,
			String viewName) {

		String valor = "";
		ArrayList<String> list = new ArrayList<String>();

		View view = myClass.getAnnotation(View.class);

		if (view != null) {

			if (view.name().equalsIgnoreCase(viewName))

				valor = view.fields();

		} else {

			Views views = myClass.getAnnotation(View.Views.class);

			if (views != null)

				for (View v : views.value())

					if (v.name().equalsIgnoreCase(viewName))

						valor = v.fields();

		}

		if (valor.length() > 0)
			for (String s : valor.split(","))
				list.add(s);

		if (list.isEmpty())
			for (Field f : myClass.getDeclaredFields())
				list.add(f.getName());

		return list;

	}

	public static ArrayList<String> getBuscarFields(Class<?> myClass) {

		String valor = "";
		ArrayList<String> list = null;

		Buscar buscar = myClass.getAnnotation(Buscar.class);

		if (buscar != null) {
			list = new ArrayList<String>();
			valor = buscar.fields();

			for (String s : valor.split(","))
				list.add(s);

		}

		return list;

	}

	public static ArrayList<String> getViewFieldsSenseIdDateUser(

	Class<?> myClass, String viewName) {

		ArrayList<String> viewAnnotationElements = getViewFields(myClass,
				viewName);
		viewAnnotationElements.remove("id");
		viewAnnotationElements.remove("user");
		viewAnnotationElements.remove("date");

		return viewAnnotationElements;
	}

	/**
	 * Retorna un objecte myClass al cual se li fa un set per cada Arg dins de
	 * args
	 * 
	 * @param myClass
	 * @param args
	 * @return IBase
	 * @throws Exception
	 */
	public static IBase setFieldArg(Class<? extends IBase> myClass,
			ArrayList<Arg> args) throws Exception {

		try {

			Field field;
			Class<?> type;
			Object valueObject = null;
			IBase objecteIBase = myClass.newInstance();

			for (Arg arg : args) {

				field = findField(myClass, arg.getName());
				type = field.getType();

				switch (type.getSimpleName()) {
				case "long":
					valueObject = Long.valueOf(arg.getValue());
					break;
				case "Integer":
					valueObject = Integer.valueOf(arg.getValue());
					break;
				case "Float":
					valueObject = Float.valueOf(arg.getValue());
					break;
				case "Date":
					valueObject = DateUtils.parseDate(arg.getValue());
					break;
				default:
					valueObject = arg.getValue();
					break;
				}

				field.set(objecteIBase, valueObject);
			}

			return objecteIBase;

		} catch (Exception e) {
			throw new Exception(e.getMessage(), e);
		}

	}

}